using Godot;

namespace MyCraft2;

public partial class BlockEditor : Node
{
    private Camera3D? PlayerCamera;
    [Export] public float ReachDistance = 5.0f;

    private readonly AudioStreamPlayer _audioStreamPlayer = new ();

    public override void _Ready()
    {
        base._Ready();
        
        AddChild(_audioStreamPlayer);

        PlayerCamera ??= GetViewport().GetCamera3D();
    }

    public override void _Input(InputEvent @event)
    {
        if (PlayerCamera is null || 
            @event is not InputEventMouseButton mouseEvent || 
            Input.MouseMode is not Input.MouseModeEnum.Captured) return;
        switch (mouseEvent)
        {
            case { ButtonIndex: MouseButton.Left, Pressed: true }:
                TryBreakBlock();
                break;
            case { ButtonIndex: MouseButton.Right, Pressed: true }:
                TryPlaceBlock();
                break;
        }
    }

    private void TryBreakBlock()
    {
        var (position, _) = RaycastFromCamera();
        if (position != null) RemoveBlock(position.Value);
    }

    private void TryPlaceBlock()
    {
        var (position, normal) = RaycastFromCamera();
        if (position != null && normal != null) AddBlock(position.Value + normal.Value);
    }


    private void AddBlock(Vector3I position)
    {
        var block = SelectedBlockRect.SelectedBlock;
        ChunkManager.SetBlock(position, block, true, true);
        _audioStreamPlayer.PlayAudioStream(block.PutAudioStream);
    }

    private void RemoveBlock(Vector3I position)
    {
        var block = ChunkManager.GetBlock(position);
        if(block is not null) _audioStreamPlayer.PlayAudioStream(block.DestroyAudioStream);
        ChunkManager.SetBlock(position, null, true, true);
    }

    private (Vector3I? position, Vector3I? normal) RaycastFromCamera()
    {
        var viewPort = GetViewport();
        var spaceState = viewPort.GetWorld3D().DirectSpaceState;
        var mousePos = viewPort.GetMousePosition();

        var from = PlayerCamera!.ProjectRayOrigin(mousePos);
        var to = from + PlayerCamera.ProjectRayNormal(mousePos) * ReachDistance;

        var query = PhysicsRayQueryParameters3D.Create(from, to);
        query.CollideWithAreas = true;

        var result = spaceState.IntersectRay(query);
        if (result.Count <= 0) return (null, null);
        var collisionPoint = result["position"].AsVector3();
        var collisionNormal = result["normal"].AsVector3();
        var blockPosition = ToBlockPosition(collisionPoint, collisionNormal);

        // 获取主轴方向作为法线
        var axis = collisionNormal.Abs().MaxAxisIndex();
        var direction = new Vector3I(
            collisionNormal.X >= 0 ? 1 : -1,
            collisionNormal.Y >= 0 ? 1 : -1,
            collisionNormal.Z >= 0 ? 1 : -1
        );

        var normalDirection = axis switch
        {
            Vector3.Axis.X => new Vector3I(direction.X, 0, 0),
            Vector3.Axis.Y => new Vector3I(0, direction.Y, 0),
            Vector3.Axis.Z => new Vector3I(0, 0, direction.Z),
            _ => Vector3I.Zero
        };

        return (blockPosition, normalDirection);
    }

    private static Vector3I ToBlockPosition(Vector3 collisionPoint, Vector3 collisionNormal)
    {
        var a = (Vector3I)collisionPoint;
        var b = collisionPoint - a;
        switch (b.X)
        {
            case <= -0.5f when collisionNormal.X >= 0:
                a.X -= 1;
                break;
            case >= 0.5f when collisionNormal.X <= 0:
                a.X += 1;
                break;
        }

        switch (b.Y)
        {
            case <= -0.5f when collisionNormal.Y >= 0:
                a.Y -= 1;
                break;
            case >= 0.5f when collisionNormal.Y <= 0:
                a.Y += 1;
                break;
        }

        switch (b.Z)
        {
            case <= -0.5f when collisionNormal.Z >= 0:
                a.Z -= 1;
                break;
            case >= 0.5f when collisionNormal.Z <= 0:
                a.Z += 1;
                break;
        }

        return a;
    }
}